<?php
/**
 * CSV helper for cakePHP. Compatible with version 1.1.x.x and higher.
 *
 * PHP versions 4 and 5
 *
 * Licensed under The MIT License
 *
 * @copyright		Adam Royle
 * @license			http://www.opensource.org/licenses/mit-license.php The MIT License
 */
class CsvHelper extends Helper
{
    var $delimiter = ',';
    var $enclosure = '"';
    var $filename = null;
    var $line = array();
    var $buffer;
    /** 
     * This option preserves leading zeros on numeric data when opened in Excel.
     * Use it ONLY when the csv file is going to be opened in Excel, as it uses
     * non-standard syntax, and will probably break in other systems.
     */
    var $preserveLeadingZerosInExcel = false;
    var $_tmpFile = false;
    function CsvHelper() 
    {
        $this->clear();
    }
    /**
     * Adds a multi-dimensional array to the buffer.
     *
     * @param array $data Multi-dimensional array
     * @param boolean $addFieldNames Add a row of field names before adding data
     * @access public
     */
    function addGrid($data, $addFieldNames = true, $fieldList = null) 
    {
        if (!$data) {
            return false;
        }
        if (@is_array(reset($row = reset($data)))) {
            // Array generated by cakePHP model
            // eg.
            // $data = array(array('Model' => array('field_name' => 'field value')), array('Model' => array('field_name' => 'field value')))
            $defaultModel = key($row);
            if ($this->filename === null) {
                $this->setFilename(Inflector::pluralize($defaultModel));
            }
            if ($fieldList) {
                $fields = array();
                foreach($fieldList as $fieldName) {
                    if (strpos($fieldName, '.')) {
                        list($modelName, $fieldName) = explode('.', $fieldName);
                    } else {
                        $modelName = $defaultModel;
                    }
                    $fields[] = array(
                        Inflector::humanize($modelName) ,
                        $fieldName
                    );
                }
                if ($addFieldNames) {
                    foreach($fields as $field) {
                        if ($field[0] != $defaultModel) {
                            $this->addField($field[0] . ' ' . Inflector::humanize($field[1]));
                        } else {
                            $this->addField(Inflector::humanize($field[1]));
                        }
                    }
                    $this->endRow();
                }
                foreach($data as $row) {
                    foreach($fields as $field) {
                        @$this->addField($row[$field[0]][$field[1]]);
                    }
                    $this->endRow();
                }
            } else {
                if ($addFieldNames) {
                    foreach(reset($row) as $key => $value) {
                        $this->addField(Inflector::humanize($key));
                    }
                    $this->endRow();
                }
                foreach($data as $row) {
                    $this->addRow($row[$defaultModel]);
                }
            }
        } else {
            // Regular 2-dimensional array (with or without keys).
            // eg.
            //			$data = array(array('field_name' => 'field_value'), array('field_name' => 'field_value'))
            //	or
            //			$data = array(array('field value'), array('field value'))
            if ($fieldList) {
                if ($addFieldNames) {
                    foreach($fieldList as $fieldName) {
                        $this->addField(Inflector::humanize($fieldName));
                    }
                    $this->endRow();
                }
                foreach($data as $row) {
                    foreach($fieldList as $fieldName) {
                        @$this->addField($row[$fieldName]);
                    }
                    $this->endRow();
                }
            } else {
                if ($addFieldNames) {
                    foreach(reset($data) as $key => $value) {
                        $this->addField(Inflector::humanize($key));
                    }
                    $this->endRow();
                }
                foreach($data as $row) {
                    $this->addRow($row);
                }
            }
        }
    }
    /**
     * Adds a single field value to the buffer. You must call $csv->endRow() to commit fields to the buffer.
     *
     * @param string $value Field value
     * @access public
     */
    function addField($value) 
    {
        $this->line[] = $value;
    }
    /**
     * Commits the row of fields that were added by addField()
     *
     * @access public
     */
    function endRow() 
    {
        $this->addRow($this->line);
        $this->line = array();
    }
    /**
     * Adds a single row to the buffer.
     *
     * @param array $row Data to be added
     * @access public
     */
    function addRow($row) 
    {
        if ($this->preserveLeadingZerosInExcel) {
            // convert the number to a string formula
            foreach($row as $key => $value) {
                if (strlen($value) > 1 && $value[0] == '0' && is_numeric($value)) {
                    $row[$key] = '="' . $value . '"';
                }
            }
        }
        fputcsv($this->buffer, $row, $this->delimiter, $this->enclosure);
    }
    /**
     * Outputs headers
     *
     * @param string $filename Filename to save as
     * @access public
     */
    function renderHeaders($filename = null) 
    {
        if (is_string($filename)) {
            $this->setFilename($filename);
        }
        if ($this->filename === null) {
            $this->filename = 'Data.csv';
        }
        if ($this->filename) {
            header('Content-disposition:attachment;filename="' . $this->filename . '"');
        }
        header('Content-type:application/vnd.ms-excel');
        header('Content-type: text/csv');
    }
    /**
     * Sets the output filename. Automatically appends .csv if necessary.
     *
     * @param string $filename Filename to save as
     * @access public
     */
    function setFilename($filename) 
    {
        $this->filename = $filename;
        if (strtolower(substr($this->filename, -4)) != '.csv') {
            $this->filename.= '.csv';
        }
    }
    /**
     * Returns CSV string and clears internal buffer. Outputs headers() if necessary.
     *
     * @param mixed $outputHeaders Boolean to determine if should output headers, or a string to set the filename
     * @param string $to_encoding Encoding to use
     * @param string $from_encoding
     * @return string String CSV formatted string
     * @access public
     */
    function render($outputHeaders = true, $to_encoding = null, $from_encoding = "auto") 
    {
        if ($outputHeaders) {
            if (is_string($outputHeaders)) {
                $this->setFilename($outputHeaders);
            }
            $this->renderHeaders();
        }
        if ($this->_tmpFile) {
            rewind($this->buffer);
            $output = '';
            while (!feof($this->buffer)) {
                $output.= fread($this->buffer, 8192);
            }
            fclose($this->buffer);
        } else {
            rewind($this->buffer);
            $output = stream_get_contents($this->buffer);
        }
        // get around excel bug (http://support.microsoft.com/kb/323626/)
        if (substr($output, 0, 2) == 'ID') {
            $pos = strpos($output, $this->delimiter);
            if ($pos === false) {
                $pos = strpos($output, "\n");
            }
            if ($pos !== false) {
                $output = $this->enclosure . substr($output, 0, $pos) . $this->enclosure . substr($output, $pos);
            }
        }
        if ($to_encoding) {
            $output = mb_convert_encoding($output, $to_encoding, $from_encoding);
        }
        $this->clear();
        return $this->output($output);
    }
    /**
     * Clears internal buffer. This is called automatically by CsvHelper::render()
     *
     * @access public
     */
    function clear() 
    {
        $this->line = array();
        $this->buffer = @fopen('php://temp/maxmemory:' . (5*1024*1024) , 'r+');
        if ($this->buffer === false) {
            $this->_tmpFile = true;
            $this->buffer = tmpfile();
        }
    }
}
/**
 * A PHP4 implementation of the equivalent PHP5 function
 *
 * See (http://www.php.net/manual/en/function.fputcsv.php) for details
 */
if (!function_exists('fputcsv')) {
    function fputcsv(&$handle, $fields = array() , $delimiter = ',', $enclosure = '"') 
    {
        $str = '';
        $escape_char = '\\';
        foreach($fields as $value) {
            settype($value, 'string');
            if (strpos($value, $delimiter) !== false || strpos($value, $enclosure) !== false || strpos($value, "\n") !== false || strpos($value, "\r") !== false || strpos($value, "\t") !== false || strpos($value, ' ') !== false) {
                $str2 = $enclosure;
                $escaped = 0;
                $len = strlen($value);
                for ($i = 0; $i < $len; $i++) {
                    if ($value[$i] == $escape_char) {
                        $escaped = 1;
                    } else if (!$escaped && $value[$i] == $enclosure) {
                        $str2.= $enclosure;
                    } else {
                        $escaped = 0;
                    }
                    $str2.= $value[$i];
                }
                $str2.= $enclosure;
                $str.= $str2 . $delimiter;
            } else {
                $str.= $value . $delimiter;
            }
        }
        $str = substr($str, 0, -1);
        $str.= "\n";
        return fwrite($handle, $str);
    }
}
?>